/*
 * ClauseGraphConverter.java
 *
 * Created on 28 ส.ค. 2550, 1:11:11
 *
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package net.siit.ke;

import com.hp.hpl.jena.rdf.model.*;
import nu.xom.*;

import java.net.*;
import java.io.*;
import java.util.regex.*;
import java.util.*;

import net.siit.text.TextUtil;
import net.siit.ling.Clause;
import net.siit.datatype.Triplet;

/**
 * @author art
 *
 */
public class ClauseGraphConverter {
    // URI declarations for RDF
    protected String RDFClauseUri;
    protected String RDFRelationshipUri;

    final public static String OPT_RDF  = "RDF";
    final public static String OPT_GRAPHML = "GraphML";
    
    public ClauseGraphConverter() {
        RDFClauseUri = "md://clause/";
        RDFRelationshipUri = "md://relation/";
    }

    public ClauseGraphConverter(String RDFClauseUri, String RDFRelationshipUri) {
        this.RDFClauseUri = RDFClauseUri;
        this.RDFRelationshipUri = RDFRelationshipUri;
    }

    public void setRDFClauseUri(String uri) {
        this.RDFClauseUri = uri;
    }

    public void setRDFRelationshipUri(String uri) {
        this.RDFRelationshipUri = uri;
    }

    public String getRDFClauseUri() {
        return RDFClauseUri;
    }

    public String getRDFRelationshipUri() {
        return RDFRelationshipUri;
    }

    /**
     *
     * @param list
     * @param outputFilename
     * @throws java.io.IOException
     */
    public void writeRDF(List<Clause> list, String outputFilename) throws IOException {
        Model model = ModelFactory.createDefaultModel();
        model.setNsPrefix("md", RDFRelationshipUri);

        //Resource phrase1 = model.createResource(clauseUri+"p1"); // S
        //Resource phrase2 = model.createResource(clauseUri+"p2"); // V
        //Resource phrase3 = model.createResource(clauseUri+"p3"); // O

        // Create properties for the different types of relationship to represent
        Property hasText   = model.createProperty(RDFRelationshipUri,"hasText");
        Property subjectOf = model.createProperty(RDFRelationshipUri,"subjectOf");
        Property objectOf  = model.createProperty(RDFRelationshipUri,"objectOf");
        Property contextOf = model.createProperty(RDFRelationshipUri,"contextOf");

        //phrase1.addProperty(subjectOf, phrase2);
        //phrase3.addProperty(objectOf, phrase2);
        //Statement statement = model.createStatement(phrase1,subjectOf,phrase2);
        //model.add(statement);

        long i = 1;
        for (Clause c : list) {
            Resource s = null, v = null, o = null, x = null;
            RDFNode ls = model.createLiteral(c.subject);
            RDFNode lv = model.createLiteral(c.verb);
            RDFNode lo = model.createLiteral(c.object);
            RDFNode lx = model.createLiteral(c.context);

            if (c.subject != null) {
                //s = model.createResource(clauseUri+ "p" + (i++)); // S
                String subject = TextUtil.removeSpaces(c.subject);
                if (subject.equals("?")) subject = "?"+(i++);
                s = model.createResource(RDFClauseUri+ subject);
                s.addProperty(hasText, c.subject);
            }
            //if (c.verb != null) {
            //        v = model.createResource(clauseUri+ "p" + (i++)); // V
            //        v.addProperty(hasText, c.verb);
            //}
            if (c.object != null) {
                //o = model.createResource(clauseUri+ "p" + (i++)); // O
                String object = TextUtil.removeSpaces(c.object);
                if (object.equals("?")) object = "?"+(i++);
                o = model.createResource(RDFClauseUri+ object);
                o.addProperty(hasText, c.object);
            }
            if (c.context != null) {
                //x = model.createResource(clauseUri+ "p" + (i++)); // X
                String context = TextUtil.removeSpaces(c.context);
                if (context.equals("?")) context = "?"+(i++);
                x = model.createResource(RDFClauseUri+ context);
                x.addProperty(hasText, c.context);
            }

            //if (s != null && v != null) {
            //    model.add(model.createStatement(s,subjectOf,v));
            //}
            //if (o != null && v != null) {
            //    model.add(model.createStatement(o,objectOf,v));
            //}

            //if (s != null) model.add();
            //if (o != null) model.add(o);

            if (s != null && o != null && c.verb != null) {
                model.add(s, model.createProperty(RDFRelationshipUri, TextUtil.removeSpaces(c.verb)), o);
            }
        }

        // if no filename specified, print to standard output
        if (outputFilename == null) {
            model.write(System.out);
        } else {
            BufferedWriter writer =
                new BufferedWriter(new OutputStreamWriter(new FileOutputStream(outputFilename), "utf-8"));
            model.write(writer);
        }
    }

    /**
     *
     * @param list
     * @param outputFilename
     * @throws java.io.IOException
     */
    public static void writeGraphML(List<Clause> list, String outputFilename) throws IOException {
        String namespace = "http://graphml.graphdrawing.org/xmlns";
        final String KEY_ID_NAME  = "name";
        final String KEY_ID_COUNT = "count";
        final String KEY_FOR_ALL  = "all";
        final String KEY_ATTR_TYPE_STRING = "string";
        final String KEY_ATTR_TYPE_INT    = "int";

        String[][] keys = {
            { KEY_ID_NAME, KEY_FOR_ALL, "name", KEY_ATTR_TYPE_STRING },
            { KEY_ID_COUNT, KEY_FOR_ALL, "count", KEY_ATTR_TYPE_INT }
        };

        Element root = new Element("graphml", namespace);
        for (String[] key : keys) {
            Element keyE = new Element("key", namespace);
            keyE.addAttribute(new Attribute("id",key[0]));
            keyE.addAttribute(new Attribute("for",key[1]));
            keyE.addAttribute(new Attribute("attr.name",key[2]));
            keyE.addAttribute(new Attribute("attr.type",key[3]));
            root.appendChild(keyE);
        }

        Element graph = new Element("graph", namespace);
        graph.addAttribute(new Attribute("edgedefault", "directed"));
        root.appendChild(graph);

        Map<Triplet,String> tripleIds = new HashMap<Triplet,String>(); // triple,id
        Map<String,Long> tripleCount = new HashMap<String,Long>();   // id,count

        Map<String,String> nounIds = new HashMap<String,String>();  // text,id
        Map<String,String> verbIds = new HashMap<String,String>();  // text,id
        Map<String,String> verbTexts = new HashMap<String,String>(); // id,text

        Map<String,Long> nounCount = new HashMap<String,Long>();  // id,count
        Map<String,Long> verbCount = new HashMap<String,Long>();  // id,count

        Long nId = 1L;
        Long vId = 1L;
        Long tId = 1L;

        for (Clause c : list) {
            String subject = makeFriendlyText(c.subject);
            String verb    = makeFriendlyText(c.verb);
            String object  = makeFriendlyText(c.object);
            String context = makeFriendlyText(c.context);

            if (subject == null) subject = "(NULL)";
            if (verb    == null) verb    = "(NULL)";
            if (object  == null) object  = "(NULL)";
            if (context == null) context = "(NULL)";

            String subjectId = null;
            String verbId    = null;
            String objectId  = null;

            subjectId = nounIds.get(subject);
            if (subjectId != null) {
                nounCount.put(subjectId, nounCount.get(subjectId) + 1L);
            } else {
                subjectId = "n" + nId;
                nounIds.put(subject, subjectId);
                nounCount.put(subjectId, 1L);
                nId++;
            }

            objectId = nounIds.get(object); // c.object could be the same as c.subject
            if (objectId != null) {
                nounCount.put(objectId, nounCount.get(objectId) + 1L);
            } else {
                objectId = "n" + nId;
                nounIds.put(object, objectId);
                nounCount.put(objectId, 1L);
                nId++;
            }

            verbId = verbIds.get(verb);
            if (verbId != null) {
                verbCount.put(verbId, verbCount.get(verbId) + 1L);
            } else {
                verbId = "v" + vId;
                verbIds.put(verb, verbId);
                verbTexts.put(verbId, verb);
                verbCount.put(verbId, 1L);
                vId++;
            }

            Triplet t = new Triplet(subjectId, verbId, objectId);
            String tripleId = tripleIds.get(t);
            if (tripleId != null) {
                tripleCount.put(tripleId, tripleCount.get(tripleId) + 1L);
            } else {
                tripleId = "e" + tId;
                tripleIds.put(t, tripleId);
                tripleCount.put(tripleId, 1L);
                tId++;
            }

            //String s = c.subject + "->" + c.verb + "->" + c.object + " (" + c.context + ")";
            //System.err.println(s);
        }

        for (String key : nounIds.keySet()) {
            String id = nounIds.get(key);
            Long count = nounCount.get(id);

            Element node = new Element("node", namespace);
            node.addAttribute(new Attribute("id", id));

            Element keyName = new Element("data", namespace);
            keyName.addAttribute(new Attribute("key", KEY_ID_NAME));
            keyName.appendChild(key);
            node.appendChild(keyName);

            Element keyCount = new Element("data", namespace);
            keyCount.addAttribute(new Attribute("key", KEY_ID_COUNT));
            keyCount.appendChild(count.toString());
            node.appendChild(keyCount);

            graph.appendChild(node);
        }

        for (Triplet t : tripleIds.keySet()) {
            String id = tripleIds.get(t);
            Long count = tripleCount.get(id);

            Element edge = new Element("edge", namespace);
            edge.addAttribute(new Attribute("id", id));
            edge.addAttribute(new Attribute("source", t.src));
            edge.addAttribute(new Attribute("target", t.trg));

            Element keyName = new Element("data", namespace);
            keyName.addAttribute(new Attribute("key", KEY_ID_NAME));
            keyName.appendChild(verbTexts.get(t.lnk));
            edge.appendChild(keyName);

            Element keyCount = new Element("data", namespace);
            keyCount.addAttribute(new Attribute("key", KEY_ID_COUNT));
            keyCount.appendChild(count.toString());
            edge.appendChild(keyCount);

            graph.appendChild(edge);

            //System.err.println("[" + id + "]("+ count + ")" + t.src + "-" + t.lnk + "->" + t.trg);
        }

        // write output
        Document doc = new Document(root);

        OutputStream out;
        if (outputFilename == null) {
            out = System.out;
        } else {
            out = new FileOutputStream(outputFilename);
        }
        Serializer serializer = new Serializer(out, "utf-8");
        serializer.setIndent(2);
        serializer.setMaxLength(76);
        serializer.write(doc);
    }

    /**
     * Make more friendly text (to put as node's label)
     * remove repeated spaces, symbols
     *
     * @param text
     * @return
     */
    private static String makeFriendlyText(String text) {
        if (text == null) return null;

        //System.err.print("[" + text + "] -- [");
        text = text.trim();
        text = TextUtil.removeRepeatedSpaces(text);

        text = text.replaceAll("[\\(\\[\\{\\:\\-]+$", "");
        text = text.replaceAll("^[\\:\\-\\}\\]\\)]+", "");

        text = text.trim();

        if (text.length() == 0) return null;

        System.err.println(text + "]");
        return text;
    }
    
    /**
     * Get the usage of the main method
     * 
     * @return
     */
    public static String getUsage() {
        return "usage: ClauseGraphConverter mode charset input_file <output_file>\n" +
               "* mode - can be 'GraphML' or 'RDF'\n" +
               "* charset - character set (encoding) e.g. 'utf-8', 'tis-620'\n" +
               "* GraphML uses two parameters 'input_file' and 'output_file'\n" +
               "* RDF uses one parameter 'input_file'\n";
    }

    /**
     * @param args
     */
    public static void main(String[] args) {
        if (args.length < 3) {
            System.err.print(getUsage());
            System.exit(1);
        }

        String mode = args[0];  // "GraphML" or "RDF"
        String charset = args[1];
        String inFilename = args[2];
        String outFilename = null;
        if (args.length >= 4) {
            outFilename = args[3];
        }
        
        if (mode.equalsIgnoreCase(OPT_RDF)) {
            if (args.length < 3) {
                System.err.print(getUsage());
                System.exit(1);
            }
        } else
        if (mode.equalsIgnoreCase(OPT_GRAPHML)) {
            if (args.length < 4) {
                System.err.print(getUsage());
                System.exit(1);
            }
        }
        
        List<Clause> list = null;
        try {
            list = ClauseUtil.collectClause(new FileInputStream(inFilename), charset);
        } catch(IOException e) {
            e.printStackTrace();
            System.exit(2);
        }
        if (list == null) {
            System.err.println("No clause to convert found, exit.");
            System.exit(3);
        }

//        Map<Clause,Long> clist = countClause(list);
//        for (Clause c : clist.keySet()) {
//            String s = c.subject + " -" + c.verb + "-> " + c.object + " (" + c.context + ")";
//            System.err.println("[" + clist.get(c) + "] " + s);
//        }

        if (mode.equalsIgnoreCase(OPT_RDF)) {
            try {
                ClauseGraphConverter converter = new ClauseGraphConverter();
                converter.writeRDF(list, outFilename);
            } catch(IOException ex) {
                System.err.println("Exception occurs while writing RDF");
                ex.printStackTrace();
                System.exit(4);
            }
        } else
        if (mode.equalsIgnoreCase(OPT_GRAPHML)) {
            try {
                writeGraphML(list, outFilename);
            } catch(IOException ex) {
                System.err.println("Exception occurs while writing GraphML");
                ex.printStackTrace();
                System.exit(4);
            }
        } 
    }
}

class Entry implements Comparable {
    String id = null;
    String text = null;
    long count = 0;

    public Entry(String id, String text, long count) {
        this.id    = id;
        this.text  = text;
        this.count = count;
    }

    @Override
    public int compareTo(Object o) {
        if (Entry.class != o.getClass())
            throw new UnsupportedOperationException("Not supported yet.");

        return text.compareTo(((Entry) o).text);
    }

    @Override
    public boolean equals(Object o) {
        try {
            if (this.compareTo(o) == 0)
                return true;
        } catch (UnsupportedOperationException e) {
            return false;
        }
        return false;
    }

    @Override
    public int hashCode() {
        int hash = 7;
        hash = 17 * hash + (this.id != null ? this.id.hashCode() : 0);
        hash = 17 * hash + (this.text != null ? this.text.hashCode() : 0);
        return hash;
    }

}
